package rs.udd.service;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.document.Document;
import org.apache.pdfbox.pdfparser.PDFParser;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import rs.udd.exceptions.UddException;
import rs.udd.model.Advert;
import rs.udd.model.Advertiser;
import rs.udd.model.Category;
import rs.udd.model.KeyWord;
import rs.udd.repository.AdvertRepository;
import rs.udd.repository.AdvertiserRepository;
import rs.udd.repository.CategoryRepository;
import rs.udd.repository.KeyWordRepository;
import rs.udd.service.document.indexer.IndexManager;
import rs.udd.service.document.indexer.UDDIndexer;
import rs.udd.service.document.indexer.handler.PDFHandler;
import rs.udd.service.dto.advert.AdvertDto;
import rs.udd.service.dto.advert.AdvertViewDto;
import rs.udd.service.mappings.AdvertMapping;
import rs.udd.service.util.FileUtil;
import rs.udd.service.util.FileUtil.FileTypes;
import rs.udd.service.util.SecurityUtil;

@Service
@Transactional
public class AdvertServiceImpl implements AdvertService {
	
	private static final Logger LOGGER = LogManager.getLogger(AdvertiserServiceImpl.class);

	@Resource
	private AdvertRepository advertRepository;
	
	@Resource
	private CategoryRepository categoryRepository;

	@Resource
	private AdvertiserRepository advertiserRepository;
	
	@Resource
	private KeyWordRepository keyWordRepository;

	@Override
	public Advert save(Advert t) throws UddException {
		try {
			return advertRepository.save(t);
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}
	
	@Override
	public AdvertDto edit(AdvertDto dto, File file) throws UddException {
		LOGGER.info("AgentServiceImpl.edit()");

		try {
			Advert advert = advertRepository.findOne(dto.getId());
			
			if ((dto.getFile() != null && !dto.getFile().isEmpty()) || file != null) {	// user uploaded new advert or user added missing meta data
				
				String oldDocumentId = advert.getIndexedDocumentId();
				String oldFilePath = advert.getFilePath();
				
				Advertiser loggedAdvertiser = SecurityUtil.getLoggedUser(Advertiser.class);
				Advertiser advertiser = advertiserRepository.findOne(loggedAdvertiser.getId());
				String originalFileName = "";
				if (file != null) {
					originalFileName = file.getName();
				} else {
					originalFileName = dto.getFile().getOriginalFilename();
				}
				String filePath = FileUtil.getFolderPathForAdvert(
						advertiser.getUsername(), FileTypes.ADVERT);
				String fileName = filePath + originalFileName;
				fileName = FileUtil.generateFileName(fileName);
				String finalFilePath = fileName;
				
				File convFile = new File(finalFilePath);
				if (file == null) {
				    convFile.createNewFile(); 
				    FileOutputStream fos = new FileOutputStream(convFile); 
				    fos.write(dto.getFile().getBytes());
				    fos.close(); 
				} else {
					convFile = file;
					fileName = file.getName();
					finalFilePath = filePath + fileName;
				}
				
				if (file == null) {	// if user is adding meta data, no need to check for them
					PDFParser parser = new PDFParser(new FileInputStream(convFile));
					IndexManager.getIndexer().parseAndValidate(parser, dto);
				}
				
				if (dto.isNotEmpty()) {	// file contains all meta data
					
					IndexManager.getIndexer().deleteDocument(oldDocumentId);
					FileUtil.cleanFile(oldFilePath);
					
					String indexedDocumentId = UDDIndexer.generateDocumentId();
					IndexManager.getIndexer().index(convFile, dto, indexedDocumentId);
					
					advert.setIndexedDocumentId(indexedDocumentId);
					advert.setFilePath(finalFilePath);
					advert.setTitle(dto.getTitle());
					advert.setAbstractText(dto.getAbstractText());

					advert.setAdvertiser(advertiser);
					advertiser.getAdverts().add(advert);
					
					Set<KeyWord> keywords = new HashSet<KeyWord>();
					for (String str : dto.getMetaKeywords().split(PDFHandler.META_DATA_SEPARATOR)) {
						String word = str.trim();
						KeyWord keyWord = keyWordRepository.findByWord(word);
						if (keyWord == null) {
							keyWord = new KeyWord();
							keyWord.setWord(word);
						}

						keyWord.getAdverts().add(advert);
						keywords.add(keyWord);
					}
					advert.setKeyWords(keywords);
					
				} else {
					FileUtil.cleanFile(fileName);
					return dto;	// return to add meta data
				}
			} else {	// setting these random values to prevent controller redirecting to add meta data page
				dto.setMetaAuthor("random");
				dto.setMetaTitle("random");
				dto.setMetaKeywords("random");
			}

			advert.setTitle(dto.getTitle());
			advert.setAbstractText(dto.getAbstractText());

			String categoryNames[] = dto.getCategoryNames().trim().split(PDFHandler.META_DATA_SEPARATOR);
			Set<Category> categories = parseCategories(categoryNames);
			for (Category category : categories) {
				category.getAdverts().add(advert);
			}
			advert.setCategories(categories);
			
			advertRepository.save(advert);

			return dto;
			
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}
	
	private Set<Category> parseCategories(String categoryNames[]) {
		Set<Category> categories = new HashSet<Category>();
		for (String categoryName : categoryNames) {
			categoryName = categoryName.trim();
			Category category = categoryRepository.findCategoryByName(categoryName);
			categories.add(category);
		}

		return categories;
	}

	@Override
	public void delete(Long id) throws UddException {
		try {
			Advert advert = advertRepository.findOne(id);
			
			boolean isDeleted = deleteAdvertInternal(advert.getIndexedDocumentId());
			if (isDeleted) {
				File advertFile = new File(advert.getFilePath());
				
				if (advertFile.exists()) {
					advertFile.delete();
					LOGGER.info("Advert file deleted.");
				}
				advertRepository.delete(id);
				LOGGER.info("Advert deleted from DB.");
			}
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}

	@Override
	public List<Advert> findAll() throws UddException {
		try {
			return advertRepository.findAll();
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}

	@Override
	public List<Advert> findAdvertsByCategory(String categoryName) throws UddException {
		try {
			List<Advert> adverts = advertRepository.findAdvertsByCategory(categoryName);
			for (Advert advert : adverts) {
				advert.getKeyWords().size(); // because keywords are lazily initialized, and this is the way to pull them from DB and show on JSP page
			}
			return adverts;
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}

	@Override
	public List<Advert> findAllForBrowse() throws UddException {
		try {
			List<Advert> adverts = advertRepository.findAll();
			for (Advert advert : adverts) {
				advert.getCategories().size();
			}
			return adverts;
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}

	@Override
	public Advert findOne(Long id) throws UddException {
		try {
			return advertRepository.findOne(id);
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}

	@Override
	public AdvertViewDto findOneForViewing(Long id) throws UddException {
		try {
			Advert advert = advertRepository.findOne(id);

			advert.getCategories().size();
			advert.getKeyWords().size();

			AdvertViewDto dto = AdvertMapping.view(advert);
			return dto;
		} catch (Exception e) {
			throw new UddException("Udd exception", e);
		}
	}
	
	@Override
	public Long getAdvertIdByIndexedDocumentId(String indexedDocumentId) {
		Advert advert = advertRepository.findByIndexedDocumentId(indexedDocumentId);
		return advert.getId();
	}
	
	private boolean deleteAdvertInternal(String id) {
		boolean deletionResult = false;
		if (StringUtils.isNotEmpty(id)) {
			
			List<Document> allDocuments = IndexManager.getIndexer().getAllDocuments();
			for (Document doc : allDocuments) {
				String documentId = doc.get(PDFHandler.ID);
				if (documentId.equals(id)) {
					deletionResult = IndexManager.getIndexer().deleteDocument(doc);
					break;
				}
			}
		}
		LOGGER.info("Advert index deleted: " + deletionResult);
		return deletionResult;
	}
}
